/// The use and distribution terms for this software are covered by the 
/// Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
/// which can be found in the file CPL.TXT at the root of this distribution.
/// By using this software in any fashion, you are agreeing to be bound by 
/// the terms of this license.
///
/// You must not remove this notice, or any other, from this software.
/// 
/// 
/// 3/13/2005 Joe Audette added handler in Application_BeginRequest 
/// for db404 error which is raised if pageid doesn't exist for siteid	
/// 
/// 6/22/2005 Joe Audette added log4net error logging	
/// 11/30/2005
/// 1/16/2006 JA added VirtualPathProvider
/// 1/29/2006 added Windows Auth support from Haluk Eryuksel
/// 2/4/2006 Joe Audette added mojoSetup 
/// 11/8/2006 Joe Audette added tracking user activity time in Application_EndRequest
/// 12/3/2006 added tracking of session count
/// 1/29/2007 added upgrade check to error handling
/// 2/9/2007 added rethrow unhandled error
/// 3/15/2007 refactor usercount increment
/// 2007/04/26 swap Principal in authenticate request
/// 2007-08-04 removed upgrade logic, its all done in Setup/Default.aspx now
/// 2007-09-20 added option to force a specific culture
/// 2009-06-24 some cleanup
/// 2009-11-20 use config settings for keepalivetask settings
/// 2011-03-14 added logic for .NET 4 to enable memory and excepton monitoring

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
#if!NET35
using System.Runtime.ExceptionServices;
#endif
using System.Security;
using System.Security.Cryptography;
using System.Threading;
using System.Web;
using System.Web.Hosting;
using System.Web.Profile;
using System.Web.Routing;
using System.Web.Security;
using log4net;
using log4net.Config;
using mojoPortal.Business;
using mojoPortal.Business.WebHelpers;
using mojoPortal.Web.Framework;
using mojoPortal.Web.Routing;
using mojoPortal.Web.Security;
using Resources;

[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]

namespace mojoPortal.Web 
{
    public class Global : HttpApplication 
	{
		private static readonly ILog log = LogManager.GetLogger(typeof(Global));

        private static bool registeredVirtualThemes = false;

        public static bool RegisteredVirtualThemes
        {
            get { return registeredVirtualThemes; }
        }

        // this changes everytime the app starts and the token is required when calling /Services/FileService.ashx
        // to help mitigate against xsrf attacks
        private static Guid fileSystemToken = Guid.NewGuid();

        public static Guid FileSystemToken
        {
            get { return fileSystemToken; }
        }

        // this changes everytime the app starts and is used for rss feed autodiscovery links so it will notredirect to feedburner
        // after each app restart the variable will change so that after the user is subscribed it will begin redirecting to feedburner if using feedburner
        private static Guid feedRedirectBypassToken = Guid.NewGuid();

        public static Guid FeedRedirectBypassToken
        {
            get { return feedRedirectBypassToken; }
        }

        private const string  RequestExceptionKey = "__RequestExceptionKey";

        private static bool appDomainMonitoringEnabled = false;

        public static bool AppDomainMonitoringEnabled
        {
            get { return appDomainMonitoringEnabled; }
        }

        private static bool firstChanceExceptionMonitoringEnabled = false;

        public static bool FirstChanceExceptionMonitoringEnabled
        {
            get { return firstChanceExceptionMonitoringEnabled; }
        }

		protected void Application_Start(Object sender, EventArgs e)
		{
            if (WebConfigSettings.EnableVirtualPathProviders)
            {
                try
                {
                    log.Info(Resource.ApplicationStartEventMessage);
                    RegisterVirtualPathProvider();

                }
                catch (MissingMethodException ex)
                {   // this is broken on mono, not implemented 2006-02-04
                    if (log.IsErrorEnabled) log.Error("Application_Start Could not register VirtualPathProvider, missing method in Mono", ex);

                }
                catch (SecurityException se)
                {
                    // must not be running in full trust
                    if (log.IsErrorEnabled) log.Error("Application_Start Could not register VirtualPathProvider, this error is expected when running in Medium trust or lower", se);

                }
                catch (UnauthorizedAccessException ae)
                {
                    // must not be running in full trust
                    if (log.IsErrorEnabled) log.Error("Application_Start Could not register VirtualPathProvider, this error is expected when running in Medium trust or lower", ae);

                }
            }

            if (WebConfigSettings.EnableRouting)
            {
                RegisterRoutes(RouteTable.Routes);
            }

            StartOrResumeTasks();

#if !NET35
            appDomainMonitoringEnabled = WebConfigSettings.AppDomainMonitoringEnabled;
            firstChanceExceptionMonitoringEnabled = WebConfigSettings.FirstChanceExceptionMonitoringEnabled;
            try
            {
                SetupMonitoring();
            }
            catch (MethodAccessException)
            {
                log.Info("Failed to setup application monitoring, not allowed under medium trust.");
                appDomainMonitoringEnabled = false;
                firstChanceExceptionMonitoringEnabled = false;
            }

#endif
            
            
		}

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.Clear();
            
            RoutingHandler.Configure(routes);

        }

#if!NET35
        private void SetupMonitoring()
        {
            
            if (appDomainMonitoringEnabled)
            {
                AppDomain.MonitoringIsEnabled = true;
            }
            if (firstChanceExceptionMonitoringEnabled)
            {
                AppDomain.CurrentDomain.FirstChanceException += (object source, FirstChanceExceptionEventArgs e) =>
                {
                    if (HttpContext.Current == null)// If no context available, ignore it
                        return;
                    if (HttpContext.Current.Items[RequestExceptionKey] == null)
                        HttpContext.Current.Items[RequestExceptionKey] = new RequestException { Exceptions = new List<Exception>() };
                    (HttpContext.Current.Items[RequestExceptionKey] as RequestException).Exceptions.Add(e.Exception);
                };
            }
        }

        private void CaptureMonitoringData()
        {
            if (!firstChanceExceptionMonitoringEnabled) { return; }

            if (Context.Items[RequestExceptionKey] != null)
            {
                //Only add the request if atleast one exception is raised
                var reqExc = Context.Items[RequestExceptionKey] as RequestException;
                reqExc.Url = Request.Url.AbsoluteUri;
                Application.Lock();
                if (Application["AllExc"] == null)
                    Application["AllExc"] = new List<RequestException>();
                (Application["AllExc"] as List<RequestException>).Add(reqExc);
                Application.UnLock();
            }
        }

#endif


        private void StartOrResumeTasks()
        {
            
            // NOTE: In IIS 7 using integrated mode, HttpContext.Current will always be null in Application_Start
            // http://weblogs.asp.net/jgaylord/archive/2008/09/04/iis7-integrated-mode-and-global-asax.aspx
            if (WebConfigSettings.UseAppKeepAlive)
            {
                AppKeepAliveTask keepAlive;
                try
                {
                    try
                    {
                        if ((HttpContext.Current != null) && (HttpContext.Current.Request != null))
                        {
                            keepAlive = new AppKeepAliveTask();
                            keepAlive.UrlToRequest = WebUtils.GetSiteRoot();
                            keepAlive.MaxRunTimeMinutes = WebConfigSettings.AppKeepAliveMaxRunTimeMinutes;
                            keepAlive.MinutesToSleep = WebConfigSettings.AppKeepAliveSleepMinutes;
                            keepAlive.QueueTask();
                        }
                    }
                    catch (HttpException)
                    {
                        //this error will be thrown when using IIS 7 Integrated pipeline mode
                        //since we have no context.Request to get the site root, in IIS 7 Integrated pipeline mode
                        //we need to use an additional config setting to get the url to request for keep alive 
                        if (WebConfigSettings.AppKeepAliveUrl.Length > 0)
                        {

                            keepAlive = new AppKeepAliveTask();
                            keepAlive.UrlToRequest = WebConfigSettings.AppKeepAliveUrl;
                            keepAlive.MaxRunTimeMinutes = WebConfigSettings.AppKeepAliveMaxRunTimeMinutes;
                            keepAlive.MinutesToSleep = WebConfigSettings.AppKeepAliveSleepMinutes;
                            keepAlive.QueueTask();

                        }

                    }
                }
                catch (Exception ex)
                {
                    // if a new installation the table will not exist yet so just log and swallow
                    log.Error(ex);
                }




            }

            
            WebTaskManager.StartOrResumeTasks(true);

        }


        private void RegisterVirtualPathProvider()
        {
            // had to move this into its own method
            // in less than full trust it blows up even with a try catch if present in 
            // Application_Start, moving into a separate method works with a try catch
            HostingEnvironment.RegisterVirtualPathProvider(new mojoVirtualPathProvider());
            registeredVirtualThemes = true;

        }


		protected void Application_End(Object sender, EventArgs e)
		{
			if (log.IsInfoEnabled) log.Info("Global.asax.cs Application_End" );
		}

        
        protected void Application_BeginRequest(Object sender, EventArgs e)
		{
            //this was an experiment that did not work as well as hoped
            //if ((Request.HttpMethod == "GET")&&(WebConfigSettings.CombineJavaScript))
            //{
            //    if (Request.AppRelativeCurrentExecutionFilePath.EndsWith(".aspx"))
            //    {
            //        Response.Filter = new ScriptDeferFilter(Response);
            //    }
            //}
           
        }


		protected void Application_EndRequest(Object sender, EventArgs e)
		{

#if!NET35
            CaptureMonitoringData();
#endif

		    // update user activity at the end of each request
            // but only if the siteUser is already in the HttpContext
            // we don't want to lookup the user for little ajax requests
            // unless we have to for security checks
		    if (HttpContext.Current.User == null) 
		        return;

            SiteUtils.TrackUserActivity();

            
		}

       



	    protected void Application_AuthenticateRequest(Object sender, EventArgs e)
		{
            //2009-05-30, moved this functionality to /Components/AuthHandlerHttpModule.cs

            
        }


		protected void Application_AuthorizeRequest(Object sender, EventArgs e)
		{
			//if (log.IsDebugEnabled) log.Debug("Global.asax.cs Application_AuthorizeRequest" );
		}


        protected void Application_Error(Object sender, EventArgs e)
        {
            bool errorObtained = false;
            Exception ex = null;

            try
            {
                Exception rawException = Server.GetLastError();
                if (rawException != null)
                {
                    errorObtained = true;
                    if (rawException.InnerException != null)
                    {
                        ex = rawException.InnerException;
                    }
                    else
                    {
                        ex = rawException;
                    }
                }
            }
            catch { }

            string exceptionUrl = string.Empty;
            string exceptionIpAddress = string.Empty;

            if (HttpContext.Current != null)
            {
                if (HttpContext.Current.Request != null)
                {
                    exceptionUrl = CultureInfo.CurrentCulture.ToString() + " - " + HttpContext.Current.Request.RawUrl;
                    exceptionIpAddress = SiteUtils.GetIP4Address();
                    
                }

            }

            if (errorObtained)
            {
                
                
                if (ex is UnauthorizedAccessException)
                {
                    // swallow this for medium trust?
                    log.Error(exceptionIpAddress + "-" + exceptionUrl, ex);
                    return;
                }

                if (ex.Message == "File does not exist.")
                {

                    log.Error(exceptionIpAddress + "-" + exceptionUrl, ex);
                    return;
                }

                

                log.Error(exceptionIpAddress + "-" + exceptionUrl, ex);

                if (ex is System.Security.Cryptography.CryptographicException)
                {
                    // hopefully this is a fix for 
                    //http://visualstudiomagazine.com/articles/2010/09/14/aspnet-security-hack.aspx
                    // at this time the exploit is not fully disclosed but seems tey use the 500 status code 
                    // so returning 404 instead may block it
                    if (WebConfigSettings.Return404StatusForCryptoError)
                    {
                        Server.ClearError();
                        try
                        {
                            if (HttpContext.Current != null)
                            {
                                if (HttpContext.Current.Response != null)
                                {
                                    HttpContext.Current.Response.Clear();

                                    // add a random delay
                                    byte[] delay = new byte[1];
                                    RandomNumberGenerator prng = new RNGCryptoServiceProvider();
                                    prng.GetBytes(delay);
                                    Thread.Sleep((int)delay[0]);
                                    IDisposable disposable = prng as IDisposable;
                                    if (disposable != null) { disposable.Dispose(); }

                                    HttpContext.Current.Response.StatusCode = 404;
                                    HttpContext.Current.Response.End();

                                    log.Info("crypto error trapped and returned a 404 instead of 500 for security reasons");
                                }
                            }
                        }
                        catch (HttpException)
                        { }

                    }

                }
                
            }

        }

        

		protected void Session_Start(Object sender, EventArgs e)
		{
			if (log.IsDebugEnabled) log.Debug("Global.asax.cs Session_Start" );
            IncrementUserCount();
		}


		protected void Session_End(Object sender, EventArgs e)
		{
            if (log.IsDebugEnabled) log.Debug("Global.asax.cs Session_End");
            DecrementUserCount();
		}


        private void IncrementUserCount()
        {
            String key = WebUtils.GetHostName() + "_onlineCount";
            if (Session != null)
            {
                Session["onlinecountkey"] = key;
            }
            if (log.IsDebugEnabled) log.Debug("IncrementUserCount key was " + key);

            Application.Lock();
            Application[key] = Application[key] == null ? 1 : (int)Application[key] + 1;
            Application.UnLock();
        }

        private void DecrementUserCount()
        {
            if (Session != null)
            {
                if (Session["onlinecountkey"] != null)
                {
                    String key = Session["onlinecountkey"].ToString();
                    if (key.Length > 0)
                    {
                        if (log.IsDebugEnabled) log.Debug("DecrementUserCount key was " + key);

                        Application.Lock();
                        int newCount = Application[key] == null ? 0 : (int)Application[key] - 1;
                        Application[key] = newCount > 0 ? newCount : 0;
                        Application.UnLock();
                    }
                }
            }
        }


        protected void Profile_MigrateAnonymous(Object sender, ProfileMigrateEventArgs args)
        {
            //TODO: maybe support capturing profile properties for anonymous users
            // then if they register transfer them to the new user

           
            //WebProfile anonymousProfile = new WebProfile(args.Context.Profile);
            

            //Profile.ZipCode = anonymousProfile;
            //Profile.CityAndState = anonymousProfile.CityAndState;
            //Profile.StockSymbols = anonymousProfile.StockSymbols;

            ////////
            // Delete the anonymous profile. If the anonymous ID is not 
            // needed in the rest of the site, remove the anonymous cookie.

            //ProfileManager.DeleteProfile(args.AnonymousID);
            //AnonymousIdentificationModule.ClearAnonymousIdentifier();

            // Delete the user row that was created for the anonymous user.
            //Membership.DeleteUser(args.AnonymousID, true);
        }

        #region Forms Authentication Handlers

        //public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
        //{
        //    if (FormsAuthentication.CookiesSupported)
        //    {
        //        if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
        //        {
        //            try
        //            {
                        

        //                FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
        //                  Request.Cookies[FormsAuthentication.FormsCookieName].Value);

                        

        //                //args.User = new System.Security.Principal.GenericPrincipal(
        //                //  new Samples.AspNet.Security.MyFormsIdentity(ticket),
        //                //  new string[0]);
        //            }
        //            catch (Exception e)
        //            {
        //                // Decrypt method failed.
        //            }
        //        }
        //    }
        //    else
        //    {
        //        throw new HttpException("Cookieless Forms Authentication is not " +
        //                                "supported for this application.");
        //    }
        //}

        #endregion


        

    }
}


